package com.strong.config.security.filter;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.strong.config.security.userdetails.JwtUserDetails;
import com.strong.utils.security.jwt.JwtTokenUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import static com.strong.config.WebSecurityConfig.STR_URL_LOGOUT_URL;
import static com.strong.utils.security.SecurityUtils.MAP_SYSTEM_USER_AUTHENTICATION;
import static com.strong.utils.security.SecurityUtils.STR_AUTHENTICATION_PREFIX;

/**
 * JWT请求过滤
 *
 * @author simen
 * @date 2022/02/10
 */
@Slf4j
@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    /**
     * jwt token 工具类
     */
    final JwtTokenUtils jwtTokenUtils;

    final AuthenticationEntryPoint authenticationEntryPoint;

    final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();

    /**
     * 实例化
     *
     * @param jwtTokenUtils jwt token 工具类
     */
    public JwtRequestFilter(JwtTokenUtils jwtTokenUtils, AuthenticationEntryPoint authenticationEntryPoint) {
        this.jwtTokenUtils = jwtTokenUtils;
        this.authenticationEntryPoint = authenticationEntryPoint;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            // 如果不是访问登出url，且通过认证
            if (!StrUtil.equals(URLUtil.getPath(request.getRequestURL().toString()), STR_URL_LOGOUT_URL) &&
                    SecurityContextHolder.getContext().getAuthentication() == null) {
                // 获取请求头Authorization
                final String strAuthorization = request.getHeader(HttpHeaders.AUTHORIZATION);
                // 判断请求Authorization非空且以STR_AUTHENTICATION_PREFIX开头
                if (StrUtil.isNotBlank(strAuthorization) && strAuthorization.startsWith(STR_AUTHENTICATION_PREFIX)) {
                    // 获取JWT Token
                    String strJwtToken = strAuthorization.replace(STR_AUTHENTICATION_PREFIX, "");

                    // 验证凭证，失败则抛出错误
                    jwtTokenUtils.verifyToken(strJwtToken);
                    // 从JWT Token中获取用户名
                    String strUserName = jwtTokenUtils.getAudience(strJwtToken);

                    // 从系统MAP中获取该用户的身份验证对象
                    UsernamePasswordAuthenticationToken authentication = MAP_SYSTEM_USER_AUTHENTICATION.get(strUserName);

                    // 判断身份验证对象非空
                    if (ObjUtil.isNotEmpty(authentication)) {
                        // 放入安全上下文中
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                        log.info(String.format("检测到[%s]访问，从系统MAP中直接获取身份验证对象", strUserName));
                    } else {
                        // 从JWT Token中获取权限字符串
                        String strAuthorities = jwtTokenUtils.getAuthorities(strJwtToken);

                        // 将用户权限放入权限列表
                        List<GrantedAuthority> listGrantedAuthority = new ArrayList<>();
                        if (StrUtil.isNotBlank(strAuthorities)) {
                            String[] strsAuthority = StrUtil.splitToArray(strAuthorities, ",");
                            for (String strAuthority : strsAuthority) {
                                listGrantedAuthority.add(new SimpleGrantedAuthority(strAuthority.trim()));
                            }
                        }

                        // 构建用户登录信息实现
                        JwtUserDetails userDetails = new JwtUserDetails(
                                strUserName, // 获取用户名
                                "[PROTECTED]", // 屏蔽密码
                                jwtTokenUtils.isToRefresh(strJwtToken), // 从token获取jwt认证是否需要刷新
                                listGrantedAuthority, jwtTokenUtils.getUserPropertiesMap(strJwtToken));
                        // 构建用户认证token
                        UsernamePasswordAuthenticationToken authenticationToken =
                                new UsernamePasswordAuthenticationToken(userDetails, null, listGrantedAuthority);
                        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        // 放入安全上下文中
                        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                        // 将身份验证对象放入系统MAP
                        MAP_SYSTEM_USER_AUTHENTICATION.put(strUserName, authenticationToken);
                        log.info(String.format("检测到[%s]访问，具有[%s]权限，缓存至系统MAP", userDetails.getUsername(), strAuthorities));
                    }
                }
            }
        } catch (AuthenticationException exception) {
            securityContextHolderStrategy.clearContext();
            authenticationEntryPoint.commence(request, response, exception);
            return;
        }
        // 使用过滤链进行过滤
        filterChain.doFilter(request, response);
    }

}
